Educational Codeforces Round 71 A~F题解

本场链接:Educational Codeforces Round 71

闲话

这场对于我1400想上1600(单场的话)要做出AE貌似,我这场只有AC的结果,看对比是+50分,然后要加200分大概出D也是不够的,如果手速快好像可以加多点就是了.然后C题真的好怪啊,,,数字位置对准花了我好长时间.读题和写代码调试的速度还是需要提高.

A. There Are Two Types Of Burgers

题目大意:一个汉堡要22片面包和11片牛肉.一份鸡腿堡要22片面包和11片鸡肉.一共有bb份面包,pp份牛肉,ff份鸡肉.一个汉堡卖hh元,一份鸡腿堡卖cc元.问最多能赚多少钱.

数据范围:
1b,p,f1001 \leq b,p,f \leq 100
1h,c1001 \leq h,c \leq 100

思路

由于数据范围极小,所以可以暴力枚举.枚举两种食物的数量,判断是否合法再计算收益即可.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int b,p,f;scanf("%d%d%d",&b,&p,&f);
		int h,c;scanf("%d%d",&h,&c);
		int res = 0;
		for(int x = 0;x <= 100;++x)
		{
			for(int y = 0;y <= 100;++y)
			{
				if(2 * (x + y) <= b && p >= x && f >= y)
				{
					res = max(res,x * h + y * c);
				}
			}
		}
		printf("%d\n",res);
    }
    return 0;
}

B. Square Filling

题目大意:一开始给你两个矩阵AABB,元素只有0011两种.BB初始是全00的.你可以往BB里染色,一旦你给(x,y)(x,y)这个位置染成了11,那么其右边,下边,右下的三个格子也必须跟着染成11.你可以在一个已经染色了的格子上染色.问你是否能把BB矩阵操作成与AA相同,如果可以,额外输出具体操作方案,注意操作方案并不要求操作次数最小.

数据范围:
2n,m502 \leq n,m \leq 50

思路

由于数据范围极小,可以暴力遍历AA中的所有元素,并针对左上角进行染色,最后判断构造出来的新矩阵是否是和AA一样的即可.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 55;
int a[N][N],b[N][N];
int main()
{
	int n,m;scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;++i)
		for(int j = 1;j <= m;++j)
			scanf("%d",&a[i][j]);
	vector<pii> op;
    for(int i = 1;i <= n - 1;++i)
    {
    	for(int j = 1;j <= m - 1;++j)
    	{
    		if(a[i][j] == 1)
    		{
    			if(a[i + 1][j] == a[i][j + 1] && a[i + 1][j + 1] == a[i][j + 1] && a[i][j + 1] == 1)
    			{
    				b[i][j] = 1;b[i][j + 1] = 1;
    				b[i + 1][j] = 1;b[i + 1][j + 1] = 1;
    				op.push_back({i,j});
    			}
    		}
    	}
    }
    for(int i = 1;i <= n;++i)
    	for(int j = 1;j <= m;++j)
    		if(a[i][j] != b[i][j])
    			return puts("-1"),0;
    printf("%d\n",op.size());
    for(auto& v : op)	printf("%d %d\n",v.first,v.second);
    return 0;
}

C. Gas Pipeline

题目大意:这鬼题没法翻译,自行看原题面吧.

数据范围:
2n21052 \leq n \leq 2*10^5
1a,b1081 \leq a,b \leq 10^8

思路

显然是dpdp求解最小的花费.这个题的坐标对准很是蛋疼,注意这里的第一维指的不是路线上的格子,而是路线上的交接点.
状态;f[i][j]f[i][j]表示走到第ii个交界点时,高度为jj的所有建造方案中花费最小的.
表示出这个状态之后,考虑有哪些特殊情况要判断是无解的:首先一个要拱起来高度达到2的点,他后面的一位在字符串里的表示应该是11.而一个11的结束的下一位是不能直接把高度往下拉到11的,他必须还要往后一位高度才能是11.对于这两种特殊情况,f[i][1]=INFf[i][1] = INF
入口:f[0][1]=b,f[1][1]=a+2b,f[1][2]=2a+3bf[0][1] = b,f[1][1] = a + 2 * b,f[1][2] = 2 * a + 3 * b
如果s[2]=1s[2] = '1'的话,说明第一位必须要建成高度为22的,此时f[1][1]=INFf[1][1] = INF表示无解.
转移:
①当s[i]==1s[i] == '1's[i+1]==1s[i + 1] == '1'f[i][1]=INFf[i][1] = INF
f[i][2]=min(f[i1][1]+2(a+b),f[i1][2]+2b+a)f[i][2] = min(f[i - 1][1] + 2 * (a + b),f[i - 1][2] + 2 * b + a)即如果本位是高度22的他可以是上一位高度为22的情况下延续过来,或者是前一位为11架高一层.
s[i]!=1s[i] != '1's[i+1]==0s[i + 1] == '0'时本位可以做高度为11的桥.此时f[i][1]=min(f[i1][2]+b+2a,f[i1][1]+a+b)f[i][1] = min(f[i - 1][2] + b + 2 * a,f[i - 1][1] + a + b)具体原因同上.
出口:f[n][1]f[n][1]因为题目要求最后一位必须是高度为11的.

不过本题还有一个边角情况就是最后一位的时候下一位是没有字符的,可以给最后一位加一个00或者特判处理掉.要不然最后一位高度为11的情况不会被计算出来.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+7;
const ll INF = (1ll << 60);
char s[N];
ll f[N][3];
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n,a,b;scanf("%d%d%d",&n,&a,&b);
    	scanf("%s",s + 1);s[n + 1] = '0';
    	memset(f,0,sizeof f);
    	f[0][1] = b;f[1][1] = a + 2 * b;f[1][2] = 2 * a + 3 * b;
    	if(s[2] == '1')	f[1][1] = INF;
    	for(int i = 2;i <= n;++i)
    	{
    		if(s[i] == '1' || s[i + 1] == '1')	f[i][1] = INF;
    		f[i][2] = min(f[i - 1][1] + 2 * (a + b),f[i - 1][2] + 2 * b + a);
    		if(s[i] != '1' && s[i + 1] == '0')	f[i][1] = min(f[i - 1][2] + b + 2 * a,f[i - 1][1] + a + b);
    	}
    	// for(int i = 1;i <= n;++i)	cout << i << " " << f[i][1] << " " << f[i][2] << endl;
    	printf("%lld\n",f[n][1]);
    }
    return 0;
}

D. Number Of Permutations

题目大意:给定nn个二元组.定义整个二元组序列不是牛逼的条件是:第一关键字是不降序列或者第二关键字是不降序列,只要满足一种就是不牛逼的.同样的,如果两者都不满足那这个二元组序列就是牛逼的.问在重排整个二元组的时候,有多少种重排方案使得这个二元组序列是牛逼的.结果对998244353取模.

思路

这题套路非常显然,就是从整体里抠掉部分有序的序列.不过这里是一个二维形式的,因此要做一个简单的冗斥,我一开始完全没意识到冗斥的事就硬做了半天.首先整个序列的排列数是非常好算的,就是n!n!,其次整个序列里要挖掉第一关键字有序的排列和第二关键字有序的排列,最后两者可能有重叠,还需要额外补上第一第二关键字同时有序的序列.
先来看第一关键字有序的序列的数量怎么求:假设整个序列里第一关键字都是不同的,没有一个重复元素,那么排列方式显然就只有一种.如果有,相同的元素之间的位置就是可以任意交换的,比如有两个的时候方案就会增加22个,显然内部排列数就是其排列数,即阶乘.而整个序列里可能有多个重复的段,每一段都是独立的,因此适用于乘法原理.因此第一关键字有序的序列数量的球法就比较明显了,就是找出所有第一关键字的出现次数,出现次数依次做阶乘,最后所有的阶乘乘起来就是答案.而第二关键字的数量与之同理.
同时有序的时候,要注意一个特殊情况,即整个序列可能不存在任何一个同时有序的序列,关于这一点可以谈心的先特殊处理出来:先对第一关键字排序再对第二关键字排序,之后考虑整个序列,如果出现了一个元素的第二关键字比后一个元素的第二关键字大的话,就说明在满足第一关键字有序的前提下,第二关键字不可能也有序.因此这种情况的答案就是00.反之,与上一种情况比较类似,就是把单个元素换成整个二元组,算出数量然后跟之前一样地算阶乘算乘积就可以了.
关于阶乘只要用递推的形式随便写写取模就完了.
这个题最后还有一个坑点,就是最后一步求答案的时候,表达式里面是存在有负数的,这个时候要应用负数取模,如果不用的话会爆掉数值.

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 3e5+7,MOD = 998244353;
#define x first
#define y second
pii a[N];
ll fact[N];
void init()
{
	fact[0] = 1;
	for(int i = 1;i < N;++i)	fact[i] = (fact[i - 1] % MOD * i % MOD) % MOD;
}
int main()
{
	init();
	int n;scanf("%d",&n);
	for(int i = 0;i < n;++i)	scanf("%d%d",&a[i].x,&a[i].y);
	sort(a , a + n);
	int secsorted = 1;
	for(int i = 0;i < n - 1;++i)
		if(a[i].y > a[i + 1].y)
		{
			secsorted = 0;
			break;
		}
	//first sorted
	ll firstres = 1;
	map<int,int> cnt;
	for(int i = 0;i < n;++i)
		++cnt[a[i].x];
	for(auto& __t : cnt)
		firstres = (firstres % MOD * fact[__t.y] % MOD) % MOD;
	//second sorted
	ll secondres = 1;
	cnt = map<int,int>();
	for(int i = 0;i < n;++i)
		++cnt[a[i].y];
	for(auto& __t : cnt)
		secondres = (secondres % MOD * fact[__t.y] % MOD) % MOD;
	//both
	ll bothres = 1;
	map<pii,int> bothcnt;
	if(secsorted)
		for(int i = 0;i < n;++i)
			++bothcnt[a[i]];
	else bothres = 0;
	for(auto& __t : bothcnt)
		bothres = (bothres % MOD * fact[__t.y] % MOD) % MOD;
	printf("%lld",((fact[n] - firstres - secondres + bothres) % MOD + MOD) % MOD);
    return 0;
}

E. XOR Guessing

题目大意:毒瘤在心里想了一个数xx,这个数x[0,2141]x \in [0,2^{14}-1],现在他要你来猜这个数.你最多可以进行两次交互,每次交互输入100100个数,毒瘤会任意选择一个数,得到xx和这个数的异或结果返回给你.输出毒瘤心里想的数.

思路

这是一道比较有意思的交互,只不过交互次数很少只有两次,考虑直接构造:定义这样几个变量:A,B,C,DA,B,C,D分别表示第一次和第二次被毒瘤选上的数,以及两者相应的异或的结果.通过两个式子很容易得到一个结论那就是AB=CDA \oplus B = C \oplus D.因此这个交互题的关键就在于构造两种序列,使得这两个序列里任意的两个元素的异或值各不相同,在各不相同的前提下才可能说找出答案是谁.
具体的构造方案是:Ai=iA_i=i,而Bi=Ai<<7B_i = A_i << 7,这样的话AiA_i与B_j,两个数异或起来就是相互独立的,并且A_i \oplus B_i = A_i + B_i,,,.,,:,这也很好理解,异或本来就是不进位加法,这样构造的数本身就不会产生进位也就是相等了. 这样构造之后,得到的结果分别取出来,就有一个式子:A+B=C\oplus D.?.那要怎么构造答案呢?因为B是来自b$序列的,所以他最后77为都是00.进而我只要把两者的和,也就是CDC\oplus D的前七位抠出来,再把后七位全部搞成00.我就得到了BB.接着,由于Bx=DB \oplus x = D,所以x=BDx = B \oplus D.最后直接输出就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[105],b[105];
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);
	for(int i = 1;i <= 100;++i)
	{
		a[i] = i;
		b[i] = (i << 7);
	}
	cout << "? ";
	for(int i = 1;i <= 100;++i)	cout << a[i] << " ";
	cout << endl;fflush(stdout);
	int c;cin >> c;fflush(stdout);
	cout << "? ";
	for(int i = 1;i <= 100;++i)	cout << b[i] << " ";
	cout << endl;fflush(stdout);
	int d;cin >> d;fflush(stdout);
	int B = (((c ^ d) >> 7) << 7);
	cout << "! " << (B ^ d);
    return 0;
}

F. Remainder Problem

原题大意:有一个长度为500000500000的序列,初始全为00.要求你支持如下两种操作:
①定点修改某一个位置上的值
②查询所有满足下标模某个数为某个数的位置上的数的和.

思路

首先暴力肯定不行,但是暴力的优化很容易想到分块.这题也是一个很明显的分块优化的idea题.具体来说,导致效率不高的点在于如果模数比较小,那么执行的操作会比较多.反之如果模数很大,那么需要统计的信息就会有效地减小.
回到这个题上来,这个题可以分开求解两种情况:当模数yy比较小的时候,用统计好的信息直接输出,在比较大的时候暴力统计.而统计信息的这一步是①操作完成的,具体来说,设置一个二维数组,第一维表示的是模数是谁,第二维表示的是具体的x%x\%这个模数的结果.当第二个操作需要使用的时候,直接输出sum[x][y]sum[x][y]即可.
注意还有一个优化点,记录所有的已经增加过到的最大的xx.在第二步操作的时候,最多只到这个xx.因为更大的全是00没有意义.

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int quasize = 707,N = 1000001;
int sum[1005][1005],a[N];
int maxn = 0;
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int opt,x,y;scanf("%d%d%d",&opt,&x,&y);
		
		if(opt == 1)
		{
			a[x] += y;maxn = max(maxn,x);
			for(int j = 1;j <= quasize;++j)
				sum[j][x % j] += y;	
		}
		else
		{
			if(x <= quasize)	printf("%d\n",sum[x][y]);
			else
			{
				int res = 0;
				for(int i = y;i <= maxn;i += x)
					res += a[i];
				printf("%d\n",res);
			}
		}
    }
    return 0;
}